#!/bin/env bash
#############################################################################
# ██╗   ██╗ ██████╗ ██╗██████╗     ██╗███╗   ██╗███████╗████████╗ █████╗ ██╗     ██╗     ███████╗██████╗
# ██║   ██║██╔═══██╗██║██╔══██╗    ██║████╗  ██║██╔════╝╚══██╔══╝██╔══██╗██║     ██║     ██╔════╝██╔══██╗
# ██║   ██║██║   ██║██║██║  ██║    ██║██╔██╗ ██║███████╗   ██║   ███████║██║     ██║     █████╗  ██████╔╝
# ╚██╗ ██╔╝██║   ██║██║██║  ██║    ██║██║╚██╗██║╚════██║   ██║   ██╔══██║██║     ██║     ██╔══╝  ██╔══██╗
#  ╚████╔╝ ╚██████╔╝██║██████╔╝    ██║██║ ╚████║███████║   ██║   ██║  ██║███████╗███████╗███████╗██║  ██║
#   ╚═══╝   ╚═════╝ ╚═╝╚═════╝     ╚═╝╚═╝  ╚═══╝╚══════╝   ╚═╝   ╚═╝  ╚═╝╚══════╝╚══════╝╚══════╝╚═╝  ╚═╝
#########################################################################################################
# Writen: Bzz
# Link: https://notabug.org/bzz/void_installer/src/main/void_install.sh
# License: BSD
# About: Script for installing Void Linux. Support LVM, LUKS. ROOTFS metod instalation OS.
# Dependencies: xz, curl
# Information:
#			[-]=waiting
#			[+]=successful
#			[!]=warning
#			[*]=info
#			[?]=aksing
#########################################################################################################

# Colors
# cl_black=$'\e[30m'
# cl_red=$'\e[31m'
# cl_green=$'\e[32m'
# cl_yellow=$'\e[33m'
# cl_blue=$'\e[34m'
# cl_magenta=$'\e[35m'
# cl_cyan=$'\e[36m'
# cl_white=$'\e[37m'
# cl_bright=$'\e[1m'
# cl_normal=$'\e[0m'
cl_red=$'\e[31m'
cl_green=$'\e[32m'
cl_blue=$'\e[34m'
cl_normal=$'\e[0m'

# Environment
luks_name="voidpc"
lvm_name="voidpc"
fs_type="ext4"
sys_hostname="voidpc"
sys_language="en_US.UTF-8"
sys_locale="en_US.UTF-8 UTF-8"
user_groups="wheel,floppy,cdrom,optical,audio,video,kvm"
grub_discards="rd.luks.allow-discards"
rootfs_link="https://repo-default.voidlinux.org/live/current/void-x86_64-ROOTFS-20240314.tar.xz"

clear

# Check is script running by root user
if [ "$(id -u)" != 0 ]; then
	printf "%s[!] Script needs to run as root%s\n" "${cl_red}" "${cl_normal}"
	exit 2
fi

# Check dependencies package curl
if ! [ -x "$(command -v curl)" ]; then
	printf "%s[!] Program curl is not installed!%s\n" "${cl_red}" "${cl_normal}"
	exit 1
fi

# Check dependencies package xz
if ! [ -x "$(command -v xz)" ]; then
	printf "%s[!] Program xz is not installed!%s\n" "${cl_red}" "${cl_normal}"
	exit 1
fi

printf "%s######################%s\n" "${cl_blue}" "${cl_normal}"
printf "%s# Install Void Linux #%s\n" "${cl_blue}" "${cl_normal}"
printf "%s######################%s\n" "${cl_blue}" "${cl_normal}"

printf "%s================================%s\n" "${cl_green}" "${cl_normal}"
printf "%s[*] Selecting disk (DEVICE:SIZE)%s\n" "${cl_green}" "${cl_normal}"
printf "%s================================%s\n" "${cl_green}" "${cl_normal}"

# Prompt user to select disk for installation
run_dev=$(lsblk -psln -o NAME "$(df --output=source / | sed 1d)" | tail -n1)
PS3="[?] Select disk for installation: "
# select _ln in $(lsblk -pdno NAME,SIZE | sed 's/ \+/:/g' | grep -v "${run_dev}"); do
select _ln in $(lsblk -pdno NAME,SIZE | sed 's/ \+/:/g'); do
	if [ -z "${_ln}" ]; then
		printf "%s[!] Wrong selection%s\n" "${cl_red}" "${cl_normal}"
		exit 1
	fi
	printf "[+] Selected disk: <%s>\n" "${_ln}"
	inst_dev=$(echo "${_ln}" | cut -d':' -f1)
	break
done

# Unmount all filesystem in /mnt folder
if grep -q " /mnt " /proc/mounts; then
	if ! umount -qR /mnt; then
		printf "%s[!] Erorr unmounting folder /mnt %s\n" "${cl_red}" "${cl_normal}"
		exit 1
	fi
fi

# Check if LVM is active
_dev=$(lsblk -pnlo NAME,FSTYPE "${inst_dev}" | grep LVM2 | awk '{ print$1 }')
if [ -n "${_dev}" ]; then
	if lvscan --devices "${_dev}" | grep -q ACTIVE; then
		_vg=$(pvscan --listvg "${_dev}" | awk '{ print$2}')
		vgchange -qan "${_vg}"
	fi
fi

# Check if LUKS is open
_name=$(lsblk -nlo NAME,TYPE "${inst_dev}" | grep "crypt$" | awk '{ print$1}')
if [ -n "${_name}" ]; then
	if cryptsetup status "${_name}" | grep -q "is active"; then
		cryptsetup close "${_name}"
	fi
fi

# Check if luks_name is open
	if cryptsetup status "${luks_name}" | grep -q "is active"; then
		printf "%s[!] LUKS volume <%s> is active%s\n" "${cl_red}" "${luks_name}" "${cl_normal}"
		exit 1
	fi

# Check if installed device is mounted
for _dev in $(lsblk -pnlo NAME "${inst_dev}"); do
	if grep -q "${_dev}" /proc/mounts; then
		printf "%s[!] Device <%s> is mounted%s\n" "${cl_red}" "${_dev}" "${cl_normal}"
		exit 1
	fi
done

# Promt for script continuation
read -rp "[?] Install to device <${inst_dev}>: (y/n) " _key
if [ "${_key}" != "y" ]; then
	printf "%s[!] Aborted!%s\n" "${cl_red}" "${cl_normal}"
	exit 1
fi

# Promt separete /boot dir
read -rp "[?] Folder /boot on separate partition: (y/n) " _key
if [ "${_key}" == "y" ]; then
	inst_boot="yes"
fi

# Wipe disk
wipefs -aqf "${inst_dev}" >/dev/null &&
	printf "[+] Wipe disk %s\n" "${inst_dev}"

# Format disklabel as DOS, create partition for LUKS
if [ "${inst_boot}" == "yes" ]; then
	printf 'label: dos\n, 1G, L, *\n, , L\n' | sfdisk -q "${inst_dev}" &&
		printf "[+] Create disklabel type <DOS> and partition\n"
	inst_dev_boot=$(lsblk -pnlo NAME "${inst_dev}" | sed -n 2p)
	inst_dev_pool=$(lsblk -pnlo NAME "${inst_dev}" | sed -n 3p)
else
	printf 'label: dos\n, , L, *\n' | sfdisk -q "${inst_dev}" &&
		printf "[+] Create disklabel type <DOS> and partition\n"
	inst_dev_pool=$(lsblk -pnlo NAME "${inst_dev}" | sed 1d)
fi

printf "%s=======================%s\n" "${cl_green}" "${cl_normal}"
printf "%s[*] Users and passwords%s\n" "${cl_green}" "${cl_normal}"
printf "%s=======================%s\n" "${cl_green}" "${cl_normal}"

# Grab informations about users and passwords
read -rsp "[?] Password for user <root>: " root_pass
printf "\n"
read -rp "[?] Non-root user: " user_name
read -rsp "[?] Password for user <${user_name}>: " user_pass
printf "\n"

printf "%s========================%s\n" "${cl_green}" "${cl_normal}"
printf "%s[*] Encrypting partition%s\n" "${cl_green}" "${cl_normal}"
printf "%s========================%s\n" "${cl_green}" "${cl_normal}"

read -rsp "[?] Password for encrypting device: " luks_pass
printf "\n"
printf "[-] Encrypting partition, wait... "

# Create LUKS encrypted partition
if [ "${inst_boot}" == "yes" ]; then
	# Create LUKS type 2
	if echo "${luks_pass}" | cryptsetup -q luksFormat "${inst_dev_pool}"; then
		printf "OK!\n"
	else
		printf "%s[!] ERROR%s\n" "${cl_red}" "${cl_normal}"
		exit 1
	fi
else
	# Create LUKS type 1, for GRUB support
	if echo "${luks_pass}" | cryptsetup -q --type luks1 luksFormat "${inst_dev_pool}"; then
		printf "OK!\n"
	else
		printf "%s[!] ERROR%s\n" "${cl_red}" "${cl_normal}"
		exit 1
	fi
fi

# Open encrypted partition
printf "[-] Opening volume to </dev/mapper/%s>... " "${luks_name}"
if echo "${luks_pass}" | cryptsetup open "${inst_dev_pool}" "${luks_name}"; then
	printf "OK!\n"
else
	printf "%s[!] ERROR%s\n" "${cl_red}" "${cl_normal}"
	exit 1
fi

printf "%s=================================%s\n" "${cl_green}" "${cl_normal}"
printf "%s[*] Creating LVM and file systems%s\n" "${cl_green}" "${cl_normal}"
printf "%s=================================%s\n" "${cl_green}" "${cl_normal}"

# Create LVM in encrypted partition, and create file systems
pvcreate /dev/mapper/${luks_name} >/dev/null &&
	printf "[+] PV </dev/mapper/%s> successfully created\n" "${luks_name}"
vgcreate ${lvm_name} /dev/mapper/${luks_name} >/dev/null &&
	printf "[+] VG <%s> successfully created\n" "${lvm_name}"

# Reading size for LVs
inst_dev_size=$(lsblk -pdno SIZE "${inst_dev_pool}" | sed 's/ \+//g')
printf "[+] Max size for LV partition - <%s>\n" "${inst_dev_size}"
read -rp "[?] Size{M,G,T,P} for LV-root (empty - all space): " lvm_root_size

# Check if size is correct
if [ -n "${lvm_root_size}" ]; then
	echo "${lvm_root_size}" | grep -qE "^[0-9]+(\.[0-9])?[MGTP]$"
	if [ $? == 1 ]; then
		printf "%s[!] Not correct size%s\n" "${cl_red}" "${cl_normal}"
		exit 1
	fi

	# Creating LVs
	lvcreate --name root -L "${lvm_root_size}" "${lvm_name}" >/dev/null &&
		printf "[+] Logical volume <root> created\n"
	lvcreate --name home -l 100%FREE ${lvm_name} >/dev/null &&
		printf "[+] Logical volume <home> created\n"

	# Creating FS for LVs
	printf "[-] Creating FS for LV root, wait... "
	mkfs.${fs_type} -qL root /dev/${lvm_name}/root && printf "OK!\n"
	printf "[-] Creating FS for LV home, wait... "
	mkfs.${fs_type} -qL home /dev/${lvm_name}/home && printf "OK!\n"
else
	lvcreate --name root -l 100%FREE ${lvm_name} >/dev/null &&
		printf "[+] Logical volume <root> created\n"

	printf "[-] Creating FS for LV root, wait... "
	mkfs.${fs_type} -qL root /dev/${lvm_name}/root && printf "OK!\n"
fi

# Creating FS for /boot
if [ "${inst_boot}" == "yes" ]; then
	printf "[-] Creating FS for boot, wait... "
	mkfs.ext2 -qFL boot "${inst_dev_boot}" && printf "OK!\n"
fi

printf "%s========================%s\n" "${cl_green}" "${cl_normal}"
printf "%s[*] Mounting FileSystems%s\n" "${cl_green}" "${cl_normal}"
printf "%s========================%s\n" "${cl_green}" "${cl_normal}"

# Mount LVM's
if [ -n "${lvm_root_size}" ]; then
	mount /dev/${lvm_name}/root /mnt &&
		printf "[+] Mount LV root to /mnt\n"
	mkdir /mnt/home
	mount /dev/${lvm_name}/home /mnt/home &&
		printf "[+] Mount LV home to /mnt/home\n"
else
	mount /dev/${lvm_name}/root /mnt &&
		printf "[+] Mount LV root to /mnt\n"
fi

# Mount boot
if [ "${inst_boot}" == "yes" ]; then
	mkdir /mnt/boot
	mount "${inst_dev_boot}" /mnt/boot &&
		printf "[+] Mount boot to /mnt/boot\n"
fi

# Mount pseudo FS's
for _fs in dev proc sys; do
	mkdir /mnt/${_fs}
	mount --rbind "/${_fs}" "/mnt/${_fs}"
	mount --make-rslave "/mnt/${_fs}"
	printf "[+] Mount /%s to /mnt/%s\n" "${_fs}" "${_fs}"
done

printf "%s====================%s\n" "${cl_green}" "${cl_normal}"
printf "%s[*] ROOTFS preparing%s\n" "${cl_green}" "${cl_normal}"
printf "%s====================%s\n" "${cl_green}" "${cl_normal}"

# Download and install base packages
printf "[-] Downloading rootfs tarball, wait... "
if curl -s -o void.tar.xz ${rootfs_link}; then
	printf "OK!\n"
else
	printf "%s[!] ERROR!!%s\n" "${cl_red}" "${cl_normal}"
	exit 1
fi

printf "[-] Extract files from archive, wait... "
if tar xf void.tar.xz -C /mnt; then
	printf "OK!\n"
else
	printf "%s[!] ERROR!!%s\n" "${cl_red}" "${cl_normal}"
	exit 1
fi

# Remove ROOTFS file
rm -f void.tar.xz >/dev/null

# Copy the DNS configuration
mkdir -p /mnt/etc
cp /etc/resolv.conf /mnt/etc/

printf "%s=================================%s\n" "${cl_green}" "${cl_normal}"
printf "%s[*] Chroot: install and configure%s\n" "${cl_green}" "${cl_normal}"
printf "%s=================================%s\n" "${cl_green}" "${cl_normal}"

# Change ownership and permissions of root directory
chroot /mnt chown root:root /
chroot /mnt chmod 755 /

# Installing base system
printf "[-] Syncing system and update xbps, wait..."
if chroot /mnt xbps-install -Suy xbps >/dev/null; then
	printf "OK\n"
else
	printf "%s[!] ERROR!!%s\n" "${cl_red}" "${cl_normal}"
	exit 1
fi

printf "[-] Updating system, wait..."
if chroot /mnt xbps-install -uy >/dev/null; then
	printf "OK\n"
else
	printf "%s[!] ERROR!!%s\n" "${cl_red}" "${cl_normal}"
	exit 1
fi

printf "[-] Installing base system, wait..."
if chroot /mnt xbps-install -y base-system lvm2 cryptsetup grub >/dev/null; then
	printf "OK\n"
else
	printf "%s[!] ERROR!!%s\n" "${cl_red}" "${cl_normal}"
	exit 1
fi

printf "[-] Removing package base-voidstrap, wait... "
if chroot /mnt xbps-remove -y base-voidstrap >/dev/null; then
	printf "OK\n"
else
	printf "%s[!] ERROR!!%s\n" "${cl_red}" "${cl_normal}"
fi

# Add and set passwords for users
printf "[+] Set password for user <root>\n"
printf "%s\n%s" "${root_pass}" "${root_pass}" | passwd -R /mnt root
printf "[-] Adding user <%s>... " "${user_name}"
chroot /mnt useradd "${user_name}" && printf "OK!\n"
printf "[-] Modifying user <%s>... " "${user_name}"
chroot /mnt usermod -aG "${user_groups}" "${user_name}" && printf "OK!\n"
printf "[+] Set password for user <%s>\n" "${user_name}"
printf "%s\n%s" "${user_pass}" "${user_pass}" | passwd -R /mnt "${user_name}"

# Set hostname and language/locale
echo "${sys_hostname}" >/mnt/etc/hostname
printf "[+] Set hostname <%s>\n" "${sys_hostname}"
echo "LANG=${sys_language}" >/mnt/etc/locale.conf
printf "[+] Set language <%s>\n" "${sys_language}"

# libc-locales are only applicable for glibc installations
echo "${sys_locale}" >>/mnt/etc/default/libc-locales
printf "[-] Reconfiguring locale to <%s>, wait... " "${sys_locale}"
chroot /mnt xbps-reconfigure -f glibc-locales >/dev/null && printf "OK!\n"

# Modify file /etc/fstab in chroot
if [ "${inst_boot}" == "yes" ]; then
	boot_uuid="UUID=\"$(lsblk -no UUID "${inst_dev_boot}")\""
	sed -n "s|${inst_dev_boot}|${boot_uuid}|; s|/mnt/boot|/boot|p" /proc/mounts >> \
		/mnt/etc/fstab
fi
grep "/dev/mapper/${lvm_name}" /proc/mounts |
	awk '$2 == "/mnt" { $2 = "/" } $2 == "/mnt/home" { $2 = "/home" } 1' >> \
		/mnt/etc/fstab
printf "[+] Update /etc/fstab\n"

# Enable LUKS in GRUB
echo "GRUB_ENABLE_CRYPTODISK=y" >>/mnt/etc/default/grub
luks_uuid=$(lsblk -ndo UUID "${inst_dev_pool}")
kernel_params="rd.lvm.vg=${lvm_name} rd.luks.uuid=$luks_uuid ${grub_discards}"
sed -i "s/\(GRUB_CMDLINE_LINUX_DEFAULT=\"\)/\1${kernel_params} /" \
	/mnt/etc/default/grub
printf "[+] Enable LUKS in GRUB\n"

# Generate keyfile to automatically unlock the encrypted volume on boot.
dd bs=1 count=64 if=/dev/urandom of=/mnt/root/volume.key status=none
echo "${luks_pass}" | cryptsetup -q luksAddKey "${inst_dev_pool}" /mnt/root/volume.key

# Change the permissions to protect generated the keyfile
chmod 000 /mnt/root/volume.key
chmod -R g-rwx,o-rwx /mnt/root/volume.key
printf "%s\tUUID=%s\t/root/volume.key\tluks\n" "${luks_name}" "${luks_uuid}" >> \
	/mnt/etc/crypttab

# Configure for dracut
printf "install_items+=\" /root/volume.key /etc/crypttab \"\n" > \
	/mnt/etc/dracut.conf.d/10-crypt.conf

# Install GRUB bootloader
printf "[-] Installing GRUB to <%s>, wait...\n" "${inst_dev}"
chroot /mnt grub-install "${inst_dev}" >/dev/null

# Ensure an initramfs is generated
printf "[-] Reconfigure system, wait...\n"
chroot /mnt xbps-reconfigure -fa >/dev/null
printf "[+] Done!\n"

printf "%s=======================%s\n" "${cl_green}" "${cl_normal}"
printf "%s[*] Swap file configure%s\n" "${cl_green}" "${cl_normal}"
printf "%s=======================%s\n" "${cl_green}" "${cl_normal}"

read -rp "[?] Use swap file: (y/n) " _key
if [ "${_key}" == "y" ]; then
	# Adding swap file
	printf "[-] Creating swap file 2Gb..."
	if dd if=/dev/zero of=/mnt/swapfile bs=2048 count=1000000; then
		printf "OK\n"
	else
		printf "[!] Error creating swap file\n"
		exit 1
	fi
	# Set up a Linux swap area
	mkswap -q /mnt/swapfile
	chmod 600 /mnt/swapfile
	printf "[+] Set up a swap file\n"
	# Adding swap file to /etc/fstab
	swap_uuid=$(swaplabel /mnt/swapfile | sed 's/:\s*/=/')
	printf "%s none swap sw 0 0\n" "${swap_uuid}" >> /mnt/etc/fstab
	printf "[+] Adding swap file to /etc/fstab\n"
else
	printf "[+] Skipping swap file configure\n"
	fi

printf "%s#######################%s\n" "${cl_blue}" "${cl_normal}"
printf "%s# Finish install Void #%s\n" "${cl_blue}" "${cl_normal}"
printf "%s#######################%s\n" "${cl_blue}" "${cl_normal}"

printf "[?] Unmount newly created Void installation: (y/n) "
read -r _key
if [[ "${_key}" == "y" ]]; then
	umount -R /mnt                # Unmount root volume
	vgchange -an "${lvm_name}"    # Deactivate volume group
	cryptsetup close ${luks_name} # Close LUKS encrypted partition
fi
